package com.ibm.psbc.cloud.common.contextcarrier;

import java.beans.PropertyDescriptor;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.core.convert.support.DefaultConversionService;

import com.ibm.iisp.common.web.springmvc.converter.DateStringConverter;
import com.ibm.iisp.common.web.springmvc.converter.StringDateConverter;

public class BeanMapUtils {
	static Logger logger = LoggerFactory.getLogger(BeanMapUtils.class);
	public static void copyBean2Map(Object bean, Map<String, String> propMap, String prefix) {
		DefaultConversionService conversionService = new DefaultConversionService();
		DateStringConverter dateConverter = new DateStringConverter();
		dateConverter.setDateTimeFormat(new SimpleDateFormat("yyyyMMdd HH:mm:ss"));
		conversionService.addConverter(dateConverter);
		copyBean2Map(bean, propMap, prefix, conversionService);
	}
	private static void copyBean2Map(Object bean, Map<String, String> propMap, String prefix, DefaultConversionService conversionService) {
		if (bean != null) {
			if (BeanUtils.isSimpleValueType(bean.getClass())) {
				String sval = conversionService.convert(bean, String.class);
				propMap.put(prefix, sval);
			} else if (bean.getClass().isArray()) {
				int idx = 0;
				for (Object v : (Object[])bean) {
					copyBean2Map(v, propMap, prefix + "[" + idx + "]", conversionService);
					idx++;
				}
			}else if ( Collection.class.isAssignableFrom(bean.getClass())) {
				@SuppressWarnings("unchecked")
				Collection<Object> cvs = (Collection<Object>) bean;
				int idx = 0;
				for (Object v : cvs) {
					copyBean2Map(v, propMap, prefix + "[" + idx + "]", conversionService);
					idx++;
				}
			}else if ( Map.class.isAssignableFrom(bean.getClass())) {
				@SuppressWarnings("unchecked")
				Map<String, ?> vmap = (Map<String, ?>) bean;
				for (Map.Entry<String, ?> vent : vmap.entrySet()) {
					String k = vent.getKey();
					Object v = vent.getValue();
					if (BeanUtils.isSimpleValueType(v.getClass())) {
						String sv = conversionService.convert(v, String.class);
						propMap.put(prefix + "." + k, sv);
					}else {
						copyBean2Map(v, propMap, prefix + "." + k, conversionService);
					}
				}
				logger.debug("map {}",bean);
			}else {
				PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(bean.getClass());
				for (PropertyDescriptor pd: propertyDescriptors) {
					if (!"class".equals(pd.getName()) && pd.getReadMethod() != null) {
						try {
							Object _val = pd.getReadMethod().invoke(bean);
							String propName = prefix == null ? pd.getName() : prefix + "." + pd.getName();
							copyBean2Map(_val, propMap, propName, conversionService);
						} catch (Exception e) {
							logger.error("Error to copy object to map.", e);
						}
					}
				}
			}
		}
	}
	public static void copyMap2Bean(Map<String, String> propMap, Object bean, String prefix) {
		if (propMap == null || propMap.isEmpty()) {
			return;
		}
		BeanWrapper beanWrap = new BeanWrapperImpl(bean);
		DefaultConversionService conversionService = new DefaultConversionService();
		StringDateConverter dateConverter = new StringDateConverter();
		dateConverter.setDateTimeFormat(new SimpleDateFormat("yyyyMMdd HH:mm:ss"));
		conversionService.addConverter(dateConverter);
		beanWrap.setConversionService(conversionService);
		beanWrap.setAutoGrowNestedPaths(true);
		if (prefix != null && prefix.length() == 0) {
			prefix = null;
		}
		for (Map.Entry<String, String> ent : propMap.entrySet()) {
			String propName = ent.getKey();
			String _val = ent.getValue();
			logger.trace("Prop: {}, value: {}", propName, _val);
			if (prefix == null || propName.startsWith(prefix)) {
				if (prefix != null ) {
					propName = propName.substring(prefix.length() + 1);
				}
				try {
					beanWrap.setPropertyValue(propName, _val);
				}catch (InvalidPropertyException e) {
					logger.info("Cannot set prop {}@{}", propName, bean.getClass());
					int dotIdx = propName.lastIndexOf(".");
					if (dotIdx > 0) {
						String mapPropName = propName.substring(0, dotIdx);
						tryMapProps(beanWrap, propName, mapPropName, _val);
					}
				}
			}
		}

		
	}
	private static void tryMapProps(BeanWrapper bean, String propName, String mapPropName, Object val) {
		try {
			PropertyDescriptor pd = bean.getPropertyDescriptor(mapPropName);
			if (pd.getPropertyType().isAssignableFrom(Map.class)) {
				@SuppressWarnings("unchecked")
				Map<String, Object> map = (Map<String, Object>) bean.getPropertyValue(mapPropName);
				String key = propName.substring(mapPropName.length() + 1);
				map.put(key, val);
				logger.trace("Put value {}={} to {}", key, val, mapPropName);
			}
		}catch (InvalidPropertyException e) {
			logger.trace("prop invalid:{}", mapPropName);
			int dotIdx = mapPropName.lastIndexOf(".");
			if (dotIdx > 0) {
				mapPropName = mapPropName.substring(0, dotIdx);
				tryMapProps(bean, propName, mapPropName, val);
			}
		}
	}}
